/************************************************************
 * Project Name         [Thinking_In_Java]
 * File Name            [FileCompressUtil.java]
 * Creation Date        [08-Jul-2014]
 * 
 * Copyright© ge.y.yang@gmail.com All Rights Reserved
 * 
 * Work hard, play harder, think big and keep fit
 ************************************************************/
package pkg_09_io.utility;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * 文件压缩与解压缩工具
 * 
 * @author 不落的太阳(Sean Yang)
 * @version 1.0
 * @since JDK 6
 * 
 */
public class FileCompressUtil {

	/**
	 * 压缩文件或者目录
	 * 
	 * @param baseDirName
	 *            压缩的根目录
	 * @param fileName
	 *            根目录下待压缩的文件或文件夹名, 星号*表示压缩根目录下的全部文件
	 * @param targetFileName
	 *            目标ZIP文件
	 */
	public static void zipFile(String baseDirName, String fileName,
			String targetFileName) {
		// 检测根目录是否存在
		if (baseDirName == null) {
			System.out.println("压缩失败, 根目录不存在: " + baseDirName);
			return;
		}
		File baseDir = new File(baseDirName);
		if (!baseDir.exists() || (!baseDir.isDirectory())) {
			System.out.println("压缩失败, 根目录不存在: " + baseDirName);
			return;
		}
		String baseDirPath = baseDir.getAbsolutePath();
		// 目标文件
		File targetFile = new File(targetFileName);
		try {
			// 创建一个zip输出流来压缩数据并写入到zip文件
			ZipOutputStream out = new ZipOutputStream(new FileOutputStream(
					targetFile));
			if (fileName.equals("*")) {
				// 将baseDir目录下的所有文件压缩到ZIP
				FileCompressUtil.dirToZip(baseDirPath, baseDir, out);
			} else {
				File file = new File(baseDir, fileName);
				if (file.isFile()) {
					FileCompressUtil.fileToZip(baseDirPath, file, out);
				} else {
					FileCompressUtil.dirToZip(baseDirPath, file, out);
				}
			}
			out.close();
			System.out.println("压缩文件成功, 目标文件名: " + targetFileName);
		} catch (IOException e) {
			System.out.println("压缩失败: " + e);
			e.printStackTrace();
		}
	}

	/**
	 * 解压缩ZIP文件, 将ZIP文件里的内容解压到targetDIR目录下
	 * 
	 * @param zipName
	 *            待解压缩的ZIP文件名
	 * @param targetBaseDirName
	 *            目标目录
	 */
	public static void unzipFile(String zipFileName, String targetBaseDirName) {
		if (!targetBaseDirName.endsWith(File.separator)) {
			targetBaseDirName += File.separator;
		}
		try {
			// 根据ZIP文件创建ZipFile对象
			@SuppressWarnings("resource")
			ZipFile zipFile = new ZipFile(zipFileName);
			ZipEntry entry = null;
			String entryName = null;
			String targetFileName = null;
			byte[] buffer = new byte[4096];
			int bytes_read;
			// 获取ZIP文件里所有的entry
			Enumeration<?> entrys = zipFile.entries();
			// 遍历所有entry
			while (entrys.hasMoreElements()) {
				entry = (ZipEntry) entrys.nextElement();
				// 获得entry的名字
				entryName = entry.getName();
				targetFileName = targetBaseDirName + entryName;
				if (entry.isDirectory()) {
					// 如果entry是一个目录, 则创建目录
					new File(targetFileName).mkdirs();
					continue;
				} else {
					// 如果entry是一个文件, 则创建父目录
					new File(targetFileName).getParentFile().mkdirs();
				}

				// 否则创建文件
				File targetFile = new File(targetFileName);
				System.out.println("创建文件: " + targetFile.getAbsolutePath());
				// 打开文件输出流
				FileOutputStream os = new FileOutputStream(targetFile);
				// 从ZipFile对象中打开entry的输入流
				InputStream is = zipFile.getInputStream(entry);
				while ((bytes_read = is.read(buffer)) != -1) {
					os.write(buffer, 0, bytes_read);
				}
				// 关闭流
				os.close();
				is.close();
			}
			System.out.println("解压缩文件成功! ");
		} catch (IOException err) {
			System.err.println("解压缩文件失败: " + err);
		}
	}

	/**
	 * 将目录压缩到ZIP输出流
	 */
	private static void dirToZip(String baseDirPath, File dir,
			ZipOutputStream out) {
		if (dir.isDirectory()) {
			// 列出dir目录下所有文件
			File[] files = dir.listFiles();
			// 如果是空文件夹
			if (files.length == 0) {
				ZipEntry entry = new ZipEntry(getEntryName(baseDirPath, dir));
				// 存储目录信息
				try {
					out.putNextEntry(entry);
					out.closeEntry();
				} catch (IOException e) {
					e.printStackTrace();
				}
				return;
			}
			for (int i = 0; i < files.length; i++) {
				if (files[i].isFile()) {
					// 如果是文件, 调用fileToZip方法
					FileCompressUtil.fileToZip(baseDirPath, files[i], out);
				} else {
					// 如果是目录, 递归调用
					FileCompressUtil.dirToZip(baseDirPath, files[i], out);
				}
			}
		}
	}

	/**
	 * 将文件压缩到ZIP输出流
	 */
	private static void fileToZip(String baseDirPath, File file,
			ZipOutputStream out) {
		FileInputStream in = null;
		ZipEntry entry = null;
		// 创建复制缓冲区
		byte[] buffer = new byte[4096];
		int bytes_read;
		if (file.isFile() && !file.getName().endsWith("zip")) {
			try {
				// 创建一个文件输入流
				in = new FileInputStream(file);
				// 构建一个ZipEntry
				entry = new ZipEntry(getEntryName(baseDirPath, file));
				// 存储项信息到压缩文件
				out.putNextEntry(entry);
				// 复制字节到压缩文件
				while ((bytes_read = in.read(buffer)) != -1) {
					out.write(buffer, 0, bytes_read);
				}
				out.closeEntry();
				in.close();
				System.out.println("添加文件" + file.getAbsolutePath()
						+ "被到ZIP文件中! ");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 获取待压缩文件在ZIP文件中entry的名字, 即相对于根目录的相对路径名
	 * 
	 * @param baseDirPath
	 * @param file
	 * @return
	 */
	private static String getEntryName(String baseDirPath, File file) {
		if (!baseDirPath.endsWith(File.separator)) {
			baseDirPath += File.separator;
		}
		String filePath = file.getAbsolutePath();
		// 对于目录, 必须在entry名字后面加上"/", 表示它将以目录项存储
		if (file.isDirectory()) {
			filePath += "/";
		}
		int index = filePath.indexOf(baseDirPath);
		return filePath.substring(index + baseDirPath.length());
	}

}
